<template>
    <div ref="tracingBeamRef" :class="cn('relative w-full max-w-4xl mx-auto h-full', $props.class)">
        <div class="absolute -left-4 top-3 md:-left-12">
            <div :style="{
                boxShadow: scrollYProgress > 0 ? 'none' : 'rgba(0, 0, 0, 0.24) 0px 3px 8px',
            }"
                class="border-netural-200 ml-[27px] flex size-4 items-center justify-center rounded-full border shadow-sm">
                <Motion :animate="{
                    backgroundColor: scrollYProgress > 0 ? 'white' : 'var(--emerald-500)',
                    borderColor: scrollYProgress > 0 ? 'white' : 'var(--emerald-600)',
                }" class="size-2 rounded-full border border-neutral-300 bg-white" />
            </div>
            <svg :viewBox="`0 0 20 ${svgHeight}`" width="20" :height="svgHeight" class="ml-4 block" aria-hidden="true">
                <path :d="`M 1 0V -36 l 18 24 V ${svgHeight * 0.8} l -18 24V ${svgHeight}`" fill="none" stroke="#9091A0"
                    stroke-opacity="0.16"></path>
                <path :d="`M 1 0V -36 l 18 24 V ${svgHeight * 0.8} l -18 24V ${svgHeight}`" fill="none"
                    stroke="url(#gradient)" stroke-width="1.25" class="motion-reduce:hidden"></path>
                <defs>
                    <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0" x2="0" :y1="spring.y1"
                        :y2="spring.y2">
                        <stop stop-color="#18CCFC" stop-opacity="0"></stop>
                        <stop stop-color="#18CCFC"></stop>
                        <stop offset="0.325" stop-color="#6344F5"></stop>
                        <stop offset="1" stop-color="#AE48FF" stop-opacity="0"></stop>
                    </linearGradient>
                </defs>
            </svg>
        </div>
        <div ref="tracingBeamContentRef">
            <slot />
        </div>
    </div>
</template>

<script lang="ts" setup>
import { Motion } from "motion-v";
import { cn } from "@/lib/utils";
import { useSpring } from "vue-use-spring";
import { ref, computed, onMounted, onUnmounted, watch } from "vue";

defineProps({
    class: String,
});

const tracingBeamRef = ref<HTMLDivElement>();
const tracingBeamContentRef = ref<HTMLDivElement>();

const scrollYProgress = ref(0);
const svgHeight = ref(0);
const scrollPercentage = ref(0);

const computedY1 = computed(
    () =>
        mapRange(scrollYProgress.value, 0, 0.8, scrollYProgress.value, svgHeight.value) *
        (1.4 - scrollPercentage.value),
);

const computedY2 = computed(
    () =>
        mapRange(scrollYProgress.value, 0, 1, scrollYProgress.value, svgHeight.value - 500) *
        (1.4 - scrollPercentage.value),
);

const spring = useSpring(
    { y1: computedY1.value, y2: computedY2.value },
    { tension: 80, friction: 26, precision: 0.01 },
);

watch(computedY1, (newY1) => {
    spring.y1 = newY1;
});

watch(computedY2, (newY2) => {
    spring.y2 = newY2;
});

function updateScrollYProgress() {
    if (tracingBeamRef.value) {
        const boundingRect = tracingBeamRef.value.getBoundingClientRect();
        const windowHeight = window.innerHeight;
        const elementHeight = boundingRect.height;

        scrollPercentage.value = (windowHeight - boundingRect.top) / (windowHeight + elementHeight);

        scrollYProgress.value = (boundingRect.y / windowHeight) * -1;
    }
}

onMounted(() => {
    window.addEventListener("scroll", updateScrollYProgress);
    window.addEventListener("resize", updateScrollYProgress);
    updateScrollYProgress();

    const resizeObserver = new ResizeObserver(function () {
        updateSVGHeight();
    });

    resizeObserver.observe(tracingBeamContentRef.value!);

    updateSVGHeight();
});

onUnmounted(() => {
    tracingBeamRef.value?.removeEventListener("scroll", updateScrollYProgress);
    window.removeEventListener("resize", updateScrollYProgress);
});

function updateSVGHeight() {
    if (!tracingBeamContentRef.value) return;

    svgHeight.value = tracingBeamContentRef.value.offsetHeight;
}

function mapRange(
    value: number,
    inMin: number,
    inMax: number,
    outMin: number,
    outMax: number,
): number {
    return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
}
</script>